using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Security.Permissions;
using System.ComponentModel;

namespace CP.Utility
{
    [HostProtection(SecurityAction.LinkDemand, SharedState = true)]
    [Serializable]
    public class SortedBindingList<T> : Collection<T>, IBindingList, IList, ICollection, IEnumerable, ICancelAddNew, IRaiseItemChangedEvents
    {
        private bool isSorted;
        private PropertyDescriptor sortProperty;
        private ListSortDirection sortDirection;
        private int addNewPos = -1;
        private bool raiseListChangedEvents = true;
        private bool raiseItemChangedEvents;
        [NonSerialized]
        private PropertyDescriptorCollection itemTypeProperties;
        [NonSerialized]
        private PropertyChangedEventHandler propertyChangedEventHandler;
        [NonSerialized]
        private AddingNewEventHandler onAddingNew;
        [NonSerialized]
        private ListChangedEventHandler onListChanged;
        [NonSerialized]
        private int lastChangeIndex = -1;
        private bool allowNew = true;
        private bool allowEdit = true;
        private bool allowRemove = true;
        private bool userSetAllowNew;
        public event AddingNewEventHandler AddingNew
        {
            add
            {
                bool flag = this.AllowNew;
                this.onAddingNew = (AddingNewEventHandler)Delegate.Combine(this.onAddingNew, value);
                if (flag != this.AllowNew)
                {
                    this.FireListChanged(ListChangedType.Reset, -1);
                }
            }
            remove
            {
                bool flag = this.AllowNew;
                this.onAddingNew = (AddingNewEventHandler)Delegate.Remove(this.onAddingNew, value);
                if (flag != this.AllowNew)
                {
                    this.FireListChanged(ListChangedType.Reset, -1);
                }
            }
        }
        public event ListChangedEventHandler ListChanged
        {
            add
            {
                this.onListChanged = (ListChangedEventHandler)Delegate.Combine(this.onListChanged, value);
            }
            remove
            {
                this.onListChanged = (ListChangedEventHandler)Delegate.Remove(this.onListChanged, value);
            }
        }
        private bool ItemTypeHasDefaultConstructor
        {
            get
            {
                Type typeFromHandle = typeof(T);
                return typeFromHandle.IsPrimitive || typeFromHandle.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.CreateInstance, null, new Type[0], null) != null;
            }
        }
        public bool RaiseListChangedEvents
        {
            get
            {
                return this.raiseListChangedEvents;
            }
            set
            {
                if (this.raiseListChangedEvents != value)
                {
                    this.raiseListChangedEvents = value;
                }
            }
        }
        private bool AddingNewHandled
        {
            get
            {
                return this.onAddingNew != null && this.onAddingNew.GetInvocationList().Length > 0;
            }
        }
        public bool AllowNew
        {
            get
            {
                if (this.userSetAllowNew || this.allowNew)
                {
                    return this.allowNew;
                }
                return this.AddingNewHandled;
            }
            set
            {
                bool flag = this.AllowNew;
                this.userSetAllowNew = true;
                this.allowNew = value;
                if (flag != value)
                {
                    this.FireListChanged(ListChangedType.Reset, -1);
                }
            }
        }
        bool IBindingList.AllowNew
        {
            get
            {
                return this.AllowNew;
            }
        }
        public bool AllowEdit
        {
            get
            {
                return this.allowEdit;
            }
            set
            {
                if (this.allowEdit != value)
                {
                    this.allowEdit = value;
                    this.FireListChanged(ListChangedType.Reset, -1);
                }
            }
        }
        bool IBindingList.AllowEdit
        {
            get
            {
                return this.AllowEdit;
            }
        }
        public bool AllowRemove
        {
            get
            {
                return this.allowRemove;
            }
            set
            {
                if (this.allowRemove != value)
                {
                    this.allowRemove = value;
                    this.FireListChanged(ListChangedType.Reset, -1);
                }
            }
        }
        bool IBindingList.AllowRemove
        {
            get
            {
                return this.AllowRemove;
            }
        }
        bool IBindingList.SupportsChangeNotification
        {
            get
            {
                return this.SupportsChangeNotificationCore;
            }
        }
        protected virtual bool SupportsChangeNotificationCore
        {
            get
            {
                return true;
            }
        }
        bool IBindingList.SupportsSearching
        {
            get
            {
                return this.SupportsSearchingCore;
            }
        }
        protected virtual bool SupportsSearchingCore
        {
            get { return true; }
        }
        bool IBindingList.SupportsSorting
        {
            get
            {
                return this.SupportsSortingCore;
            }
        }
        protected virtual bool SupportsSortingCore
        {
            get { return true; }
        }
        bool IBindingList.IsSorted
        {
            get
            {
                return this.IsSortedCore;
            }
        }
        protected virtual bool IsSortedCore
        {
            get { return isSorted; }
        }
        PropertyDescriptor IBindingList.SortProperty
        {
            get
            {
                return this.SortPropertyCore;
            }
        }
        protected virtual PropertyDescriptor SortPropertyCore
        {
            get { return sortProperty; }
        }
        ListSortDirection IBindingList.SortDirection
        {
            get
            {
                return this.SortDirectionCore;
            }
        }
        protected virtual ListSortDirection SortDirectionCore
        {
            get { return sortDirection; }
        }
        bool IRaiseItemChangedEvents.RaisesItemChangedEvents
        {
            get
            {
                return this.raiseItemChangedEvents;
            }
        }
        #region custom fields
        int m_currentIndex = -1;
        Dictionary<int, T> OriginalItems;
        //Dictionary<int, T> NewItems;
        IList<int> ChangedIndexes;
        IList<int> AddedIndexes;
        IList<int> DeletedIndexes;
        #endregion
        #region custom methods
        public IList<T> GetChanges(ListChangedType changedtype)
        {
            if (changedtype == ListChangedType.ItemAdded)
                return GetAddedItems();
            if (changedtype == ListChangedType.ItemChanged)
                return GetChangedItems();
            if (changedtype == ListChangedType.ItemDeleted)
                return GetDeletedItems();
            return null;
        }
        private IList<T> GetAddedItems()
        {
            if ((null == AddedIndexes) || (0 == AddedIndexes.Count))
                return null;
            IList<T> list = new List<T>();
            for (int i = 0; i < AddedIndexes.Count; i++)
            {
                foreach (T item in Items)
                {
                    if (item.GetAttachedPropertyValue<int>("oldindex") == AddedIndexes[i])
                        list.Add(item);
                }
            }
            return list;
        }
        private IList<T> GetChangedItems()
        {
            if ((null == ChangedIndexes) || (0 == ChangedIndexes.Count))
                return null;
            IList<T> list = new List<T>();
            for (int i = 0; i < ChangedIndexes.Count; i++)
            {
                foreach (T item in Items)
                {
                    if (item.GetAttachedPropertyValue<int>("oldindex") == ChangedIndexes[i])
                        list.Add(item);
                }
            }
            return list;
        }
        private IList<T> GetDeletedItems()
        {
            if ((null == DeletedIndexes) || (0 == DeletedIndexes.Count))
                return null;
            IList<T> list = new List<T>();
            for (int i = 0; i < DeletedIndexes.Count; i++)
            {
                list.Add(OriginalItems[DeletedIndexes[i]]);
            }
            return list;
        }
        #endregion
        public SortedBindingList()
        {
            this.Initialize();
        }
        public SortedBindingList(IList<T> list)
            : base(list)
        {
            this.Initialize();
        }
        private void Initialize()
        {
            this.allowNew = this.ItemTypeHasDefaultConstructor;
            if (typeof(INotifyPropertyChanged).IsAssignableFrom(typeof(T)))
            {
                this.raiseItemChangedEvents = true;
                foreach (T current in base.Items)
                {
                    this.HookPropertyChanged(current);
                }
            }
        }
        protected virtual void OnAddingNew(AddingNewEventArgs e)
        {
            if (this.onAddingNew != null)
            {
                this.onAddingNew(this, e);
            }
        }
        private object FireAddingNew()
        {
            AddingNewEventArgs addingNewEventArgs = new AddingNewEventArgs(null);
            this.OnAddingNew(addingNewEventArgs);
            return addingNewEventArgs.NewObject;
        }
        protected virtual void OnListChanged(ListChangedEventArgs e)
        {
            if (this.onListChanged != null)
            {
                this.onListChanged(this, e);
            }
        }
        public void ResetBindings()
        {
            this.FireListChanged(ListChangedType.Reset, -1);
        }
        public void ResetItem(int position)
        {
            this.FireListChanged(ListChangedType.ItemChanged, position);
        }
        private void FireListChanged(ListChangedType type, int index)
        {
            if (this.raiseListChangedEvents)
            {
                this.OnListChanged(new ListChangedEventArgs(type, index));
            }
        }
        //?
        protected override void ClearItems()
        {
            this.EndNew(this.addNewPos);
            if (this.raiseItemChangedEvents)
            {
                foreach (T current in base.Items)
                {
                    this.UnhookPropertyChanged(current);
                }
            }
            base.ClearItems();
            this.FireListChanged(ListChangedType.Reset, -1);
        }
        protected override void InsertItem(int index, T item)
        {
            item.SetAttachedPropertyValue<int>("oldindex", ++m_currentIndex);
            this.EndNew(this.addNewPos);
            base.InsertItem(index, item);
            if (this.raiseItemChangedEvents)
            {
                this.HookPropertyChanged(item);
            }
            this.FireListChanged(ListChangedType.ItemAdded, index);
            //modified by asingna 2011-12-29 16:08
            //save item to original items according to current changedtype
            //if not addnew method call,than do
            if (m_changedType == ListChangedType.Reset)
            {
                if (null == OriginalItems)
                    OriginalItems = new Dictionary<int, T>();
                object obj = item.CopyObject();
                if (null != obj)
                {
                    OriginalItems.Add(m_currentIndex, (T)obj);
                }
            }
            else
            {
                if (null == AddedIndexes)
                    AddedIndexes = new List<int>();
                AddedIndexes.Add(m_currentIndex);
            }
            //NewItems.Add(m_currentIndex, item);
        }
        //if the index in the originalitems return true
        private bool IsOldIndex(int index)
        {
            return OriginalItems.ContainsKey(index);
        }
        //if the index to be deleting in the added,changed,deleted then 
        //modify the indexes bigger than the index -1 and
        //delete the index in the added,changed
        private void IndexDiscrease(int index)
        {
            if (null != AddedIndexes)
            {
                IndexesSub(AddedIndexes, index);
            }
            if (null != ChangedIndexes)
            {
                IndexesSub(ChangedIndexes, index);
            }
            if (null != DeletedIndexes)
            {
                IndexesSub(DeletedIndexes, index);
            }
        }
        private void IndexesSub(IList<int> list, int index)
        {
            if (0 <= index)
                list.Remove(index);
            //NewItems.Remove(index);
        }
        private int GetOldIndex(int index)
        {
            return Items[index].GetAttachedPropertyValue<int>("oldindex");
        }
        protected override void RemoveItem(int index)
        {
            int oldindex = GetOldIndex(index);
            IndexDiscrease(oldindex);
            if (IsOldIndex(oldindex))
            {
                if (null == DeletedIndexes)
                    DeletedIndexes = new List<int>();
                DeletedIndexes.Add(oldindex);
            }

            if (!this.allowRemove && (this.addNewPos < 0 || this.addNewPos != index))
            {
                throw new NotSupportedException();
            }
            this.EndNew(this.addNewPos);
            if (this.raiseItemChangedEvents)
            {
                this.UnhookPropertyChanged(base[index]);
            }
            base.RemoveItem(index);
            this.FireListChanged(ListChangedType.ItemDeleted, index);
        }
        protected override void SetItem(int index, T item)
        {
            if (this.raiseItemChangedEvents)
            {
                this.UnhookPropertyChanged(base[index]);
            }
            base.SetItem(index, item);
            if (this.raiseItemChangedEvents)
            {
                this.HookPropertyChanged(item);
            }
            this.FireListChanged(ListChangedType.ItemChanged, index);
        }
        public virtual void CancelNew(int itemIndex)
        {
            if (this.addNewPos >= 0 && this.addNewPos == itemIndex)
            {
                this.RemoveItem(this.addNewPos);
                this.addNewPos = -1;
            }
        }
        public virtual void EndNew(int itemIndex)
        {
            if (this.addNewPos >= 0 && this.addNewPos == itemIndex)
            {
                this.addNewPos = -1;
            }
        }
        private ListChangedType m_changedType = ListChangedType.Reset;
        private void SetCurrentChangedType(ListChangedType changedType)
        {
            this.m_changedType = changedType;
        }
        public T AddNew()
        {
            this.SetCurrentChangedType(ListChangedType.ItemAdded);
            T newobj = (T)((IBindingList)this).AddNew();
            this.SetCurrentChangedType(ListChangedType.Reset);
            return newobj;
        }
        object IBindingList.AddNew()
        {
            this.SetCurrentChangedType(ListChangedType.ItemAdded);
            object obj = this.AddNewCore();
            this.addNewPos = ((obj != null) ? base.IndexOf((T)obj) : -1);
            this.SetCurrentChangedType(ListChangedType.Reset);
            return obj;
        }
        protected virtual object AddNewCore()
        {
            object obj = this.FireAddingNew();
            if (obj == null)
            {
                Type typeFromHandle = typeof(T);
                obj = Activator.CreateInstance(typeFromHandle);
                //obj = SecurityUtils.SecureCreateInstance(typeFromHandle);
            }
            base.Add((T)obj);
            return obj;
        }
        void IBindingList.ApplySort(PropertyDescriptor prop, ListSortDirection direction)
        {
            this.ApplySortCore(prop, direction);
        }
        protected virtual void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction)
        {
            List<T> items = this.Items as List<T>;

            if (items != null)
            {
                ObjectPropertyCompare<T> pc = new ObjectPropertyCompare<T>(prop, direction);
                items.Sort(pc);
                isSorted = true;
            }
            else
            {
                isSorted = false;
            }

            sortProperty = prop;
            sortDirection = direction;

            this.OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
        }
        public void Sort(PropertyDescriptor property, ListSortDirection direction)
        {
            this.ApplySortCore(property, direction);
        }
        void IBindingList.RemoveSort()
        {
            this.RemoveSortCore();
        }
        protected virtual void RemoveSortCore()
        {
            isSorted = false;
            this.OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));

        }
        int IBindingList.Find(PropertyDescriptor prop, object key)
        {
            return this.FindCore(prop, key);
        }
        protected virtual int FindCore(PropertyDescriptor prop, object key)
        {
            throw new NotSupportedException();
        }
        void IBindingList.AddIndex(PropertyDescriptor prop)
        {
        }
        void IBindingList.RemoveIndex(PropertyDescriptor prop)
        {
        }
        private void HookPropertyChanged(T item)
        {
            INotifyPropertyChanged notifyPropertyChanged = item as INotifyPropertyChanged;
            if (notifyPropertyChanged != null)
            {
                if (this.propertyChangedEventHandler == null)
                {
                    this.propertyChangedEventHandler = new PropertyChangedEventHandler(this.Child_PropertyChanged);
                }
                notifyPropertyChanged.PropertyChanged += this.propertyChangedEventHandler;
            }
        }
        private void UnhookPropertyChanged(T item)
        {
            INotifyPropertyChanged notifyPropertyChanged = item as INotifyPropertyChanged;
            if (notifyPropertyChanged != null && this.propertyChangedEventHandler != null)
            {
                notifyPropertyChanged.PropertyChanged -= this.propertyChangedEventHandler;
            }
        }
        private void Child_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {

            if (this.RaiseListChangedEvents)
            {
                if (sender == null || e == null || string.IsNullOrEmpty(e.PropertyName))
                {
                    this.ResetBindings();
                    return;
                }
                T t;
                try
                {
                    t = (T)sender;
                }
                catch (InvalidCastException)
                {
                    this.ResetBindings();
                    return;
                }
                int num = this.lastChangeIndex;
                if (num >= 0 && num < base.Count)
                {
                    T t2 = base[num];
                    if (t2.Equals(t))
                    {
                        goto IL_7B;
                    }
                }
                num = base.IndexOf(t);
                this.lastChangeIndex = num;
            IL_7B:
                if (num == -1)
                {
                    this.UnhookPropertyChanged(t);
                    this.ResetBindings();
                    return;
                }
                if (this.itemTypeProperties == null)
                {
                    this.itemTypeProperties = TypeDescriptor.GetProperties(typeof(T));
                }
                PropertyDescriptor propDesc = this.itemTypeProperties.Find(e.PropertyName, true);
                ListChangedEventArgs e2 = new ListChangedEventArgs(ListChangedType.ItemChanged, num, propDesc);
                this.OnListChanged(e2);
                //the num in the originalitems then add to changedindexes
                if (num != -1)
                {
                    if (IsOldIndex(num))
                    {
                        if (null == ChangedIndexes)
                            ChangedIndexes = new List<int>();
                        ChangedIndexes.Add(num);
                    }
                }
            }
        }
    }
}
